/*
 * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
 * Copyright (C) 2024, 2025  Mio
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
module app.cmds.artist;

import pd.configuration;
import pd.pixiv;

int artistHandle(string[] args, const ref Config config)
{
   import std.algorithm.searching : all, find;
   import std.getopt : getopt, GetoptOptions = config;
   import std.stdio : stderr, stdout;
   import app.util : sleep;

   /* artist <id> */
   if (args.length < 2) {
      displayArtistHelp();
      return 3;
   }

   string enteredType;
   int mangaOffset;
   int illustOffset;
   int novelOffset;

   const helpInformation = getopt(args,
      GetoptOptions.passThrough,
      "type|t", &enteredType,
      "skip-illust", &illustOffset,
      "skip-manga", &mangaOffset,
      "skip-novel", &novelOffset);

   if (helpInformation.helpWanted) {
      displayArtistHelp();
      return 0;
   }

   bool pred(string arg) { return arg.all!("a >= '0' && a <= '9'"); }
   string[] ids = find!(pred)(args);

   if (enteredType != "illust" && enteredType != "manga" &&
         enteredType != "novel" && enteredType != "") {
      stderr.writefln("%s: Invalid Content Type: %s", args[0], enteredType);
      stderr.writeln("Valid Content Types are: 'illust', 'manga', and 'novel'.");
      stderr.writeln("To download all content, do not use the --type option.");
      return 3;
   }

   // --skip-manga and --skip-illust are only valid when there is one
   // account ID provided.
   if (ids.length > 1 && (mangaOffset != 0 || illustOffset != 0 || novelOffset != 0)) {
      stderr.writefln("%s: Cannot provide an offset for more than one artist.",
         args[0]);
      stderr.writefln("Run '%s artist --help' for more information.", args[0]);
      return 1;
   }

   foreach (id; ids) {
      switch (enteredType) {
      case "illust":
         downloadArtist(id, "illust", illustOffset, config);
         break;
      case "manga":
         downloadArtist(id, "manga", mangaOffset, config);
         break;
      case "novel":
         downloadArtist(id, "novel", novelOffset, config);
         break;
      default:
         downloadArtist(id, "novel", novelOffset, config);
         sleep(5, 10);
         downloadArtist(id, "manga", mangaOffset, config);
         sleep(5, 10);
         downloadArtist(id, "illust", illustOffset, config);
         break;
      }
      sleep(10, 20);
   }

   return 0;
}

///
/// Display the help message for the "artist" command.
///
void displayArtistHelp()
{
   import std.stdio : stderr;

   stderr.writeln("pixiv_down artist - Download content from specified artists\n" ~
         "\nUsage:\tpixiv_down artist [options] <IDs...>\n" ~
         "\nBy default, running the command with no options will result in\n" ~
         "downloading all content from each supported content type. You\n" ~
         "can change this by using the \"--type\" option.\n" ~
         "\nOptions:\n" ~
         "   -h, --help   \tDisplay this help message and exit.\n" ~
         "   -t, --type   \tLimit the type of content to download.\n" ~
         "                \t      Valid values are: illust, manga, novel\n" ~
         "   --skip-manga \tThe number of manga to skip when\n" ~
         "                \tdownloading the specified artist.\n" ~
         "   --skip-illust\tThe number of illustrations to skip when\n" ~
         "                \tdownloading the specified artist.\n" ~
         "   --skip-novel \tThe number of novels to skip when\n" ~
         "                \tdownloading the specified artist.\n" ~
         "\nExamples:\n" ~
         "\n  Download all content from an artist:\n" ~
         "       pixiv_down artist 10109777\n" ~
         "\n  Download all manga from an artist:\n" ~
         "       pixiv_down artist --type manga 10109777\n" ~
         "\n  Download all illustrations from multiple artists:\n" ~
         "       pixiv_down artist --type illust 10109777 4938312\n" ~
         "\n  Skip the first 10 illustrations from an artist.\n" ~
         "       pixiv_down artist --skip-illust 10 10109777\n" ~
         "\nNotes:\n" ~
         "The --skip-manga, --skip-illust, and --skip-novel options only work\n" ~
         "when a single artist ID has been provided. They will NOT restrict\n" ~
         "the content type downloaded.");
}

private:

void downloadArtist(string id, string type, int offset, const ref Config config)
{
   import std.experimental.logger;
   import std.net.curl : CurlException;
   import std.stdio : stdout, stderr;
   import pd.pixiv;
   import pd.pixiv_downloader;
   import mlib.term;
   import app.util : sleep;
   import pd.error_cache;

   const user = fetchUser(id, config);
   const userProfile = fetchUserProfie(id, config);
   ErrorCache errorCache = loadErrorCache();
   scope(exit) save(errorCache);

   if ("illust" == type) {
      infof("number of illustrations to download: %d", userProfile.illusts.length);

      if (0 == userProfile.illusts.length) {
         stdout.writefln("No illustrations to download from %s.", user.userName);
         return;
      } else if (offset >= userProfile.illusts.length) {
         stdout.writefln("No illustrations to download from %s - they were all skipped!", user.userName);
         return;
      }

      stdout.writefln("Downloading illustrations by %s.", user.userName);
      foreach (elemNum, illustId; userProfile.illusts[offset..$]) {
         auto info = fetchArtworkInfo(illustId, config);

         try {
            downloadArtwork(info, config);
	    errorCache.artworks.removeKey(info.id);
         } catch (CurlException ce) {
            errorf("exception in curl: %s", ce.msg);
            Term.clearCurrentLine(Yes.useStderr);
            stderr.writefln("Failed to download %s. Will retry.", info.type);
            sleep(20, 30);
            downloadArtwork(info, config);
	    errorCache.artworks.removeKey(info.id);
        } catch (Exception e) {
            Term.clearCurrentLine(Yes.useStderr);
            stderr.writefln("Failed to download %s.", info.type);
            errorCache.artworks.insert(info.id);
        }

         if (elemNum == 0 || elemNum % 10 != 0) {
            sleep(4, 10);
         } else {
            sleep(10, 20);
         }
         Term.goUpAndClearLine(1, Yes.useStderr);
      }
      stdout.writefln("Downloaded illustrations by %s.", user.userName);
   }
   else if ("manga" == type) {
      infof("Number of manga to download: %d", userProfile.manga.length);

      if (0 == userProfile.manga.length) {
         stdout.writefln("No manga to download from %s.", user.userName);
         return;
      } else if (offset >= userProfile.manga.length) {
         stdout.writefln("No manga to download from %s - they were all skipped!", user.userName);
         return;
      }

      stdout.writefln("Downloading manga by %s.", user.userName);
      foreach (elemNum, illustId; userProfile.manga[offset..$]) {
         ArtworkInfo info = fetchArtworkInfo(illustId, config);

         try {
            downloadArtwork(info, config);
	    errorCache.artworks.removeKey(info.id);
         } catch (CurlException ce) {
            errorf("exception in curl: %s", ce.msg);
            Term.clearCurrentLine(Yes.useStderr);
            stderr.writefln("Failed to download %s. Will retry.", info.type);
            sleep(20, 30);
            downloadArtwork(info, config);
	    errorCache.artworks.removeKey(info.id);
        } catch (Exception e) {
            Term.clearCurrentLine(Yes.useStderr);
            stderr.writefln("Failed to download %s.", info.type);
            errorCache.artworks.insert(info.id);
        }

         if (elemNum == 0 || elemNum % 10 != 0) {
            sleep(4, 10);
         } else {
            sleep(10, 20);
         }
         Term.goUpAndClearLine(1, Yes.useStderr);
      }
      stdout.writefln("Downloaded manga by %s.", user.userName);
   }
   else if ("novel" == type) {
      infof("Number of novels to download: %d", userProfile.novels.length);

      if (0 == userProfile.novels.length) {
         stdout.writefln("No novels to download from %s.", user.userName);
         return;
      }
      if (offset >= userProfile.novels.length) {
         stdout.writefln("No novels to download from %s -- they were all skipped!", user.userName);
         return;
      }

      stdout.writefln("Downloading novels by %s", user.userName);
      foreach(elemNum, novelId; userProfile.novels[offset..$]) {
         auto info = fetchNovelInfo(novelId, config);

         try {
            downloadNovel(info, config);
	    errorCache.novels.removeKey(info.id);
         } catch (CurlException ce) {
            errorf("exception in curl: %s", ce.msg);
            Term.clearCurrentLine(Yes.useStderr);
            stderr.writeln("Failed to download novel. Will retry.");
            sleep(20, 30);
            downloadNovel(info, config);
	    errorCache.novels.removeKey(info.id);
        } catch (Exception e) {
            Term.clearCurrentLine(Yes.useStderr);
            errorCache.novels.insert(info.id);
            stderr.writeln("Failed to download novel.");
        }

         if (elemNum == 0 || elemNum % 10 != 0) {
            sleep(4, 10);
         } else {
            sleep(10, 20);
         }
         Term.goUpAndClearLine(1, Yes.useStderr);
      }
      stdout.writefln("Downloaded novels by %s.", user.userName);
   }
   else
   {
      import std.format : format;

      string msg = format!"Unsupported operation (DOWNLOADING %s)"(type);
      throw new Exception(msg);
   }
}
